package edu.spbsu.nonsm00th.qdiff.parser;

import edu.spbsu.nonsm00th.qdiff.parser.exception.ArgumentIndexOutOfRangeException;
import edu.spbsu.nonsm00th.qdiff.parser.exception.ParserException;
import edu.spbsu.nonsm00th.qdiff.parser.exception.UnknownCharException;
import edu.spbsu.nonsm00th.qdiff.parser.exception.WordNotFoundException;
import edu.spbsu.nonsm00th.qdiff.parser.word.Argument;
import edu.spbsu.nonsm00th.qdiff.parser.word.Control;
import edu.spbsu.nonsm00th.qdiff.parser.word.Function;
import edu.spbsu.nonsm00th.qdiff.parser.word.Operation;
import edu.spbsu.nonsm00th.qdiff.parser.word.Number;
import edu.spbsu.nonsm00th.qdiff.parser.word.Unit;
import edu.spbsu.nonsm00th.qdiff.parser.word.Word;
import edu.spbsu.nonsm00th.util.Precondition;
import edu.spbsu.nonsm00th.util.StringUtil;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * Expression parser class.
 *
 * @author mbuzdov
 */
public class ExpressionParser {

    private char[] sequence;
    private int position;
    private OperationParser oparser;
    private FunctionParser fparser;
    private ControlParser ceparser;
    private NumberParser nparser;
    private ArgumentParser aparser;

    public ExpressionParser() {
        oparser = new OperationParser();
        fparser = new FunctionParser();
        ceparser = new ControlParser();
        nparser = new NumberParser();
        aparser = new ArgumentParser();
    }

    private void setInitialData(String expression) {
        sequence = expression.toCharArray();
        position = 0;
    }

    public List<Unit> parse(String expression) throws ParserException {
        Precondition.check(!StringUtil.isNullOrEmpty(expression), "Expression can not be empty!");
        setInitialData(expression);

        List<Unit> result = new ArrayList<Unit>();
        while (position < sequence.length) {
            if (sequence[position] <= ' ') { //whitespaces
                position++;
                continue;
            }
            int from = position;
            Word next = getNext();
            if (null == next) {
                throw new UnknownCharException(position, sequence[position]);//
            }
            result.add(new Unit(next, from));
        }
        return result;
    }

    private Word getNext() throws ParserException { // TODO: refactor this part
        if (isCurrentCharFrom("xX")) {//TODO: when throw WordNotFoundException we return last position of word, not startPosition
            return getNextArgument();
        }
        if (isCurrentCharFrom(Operation.ALPHABET)) {
            return getNextOperation();
        }
        if (isCurrentCharFrom(Function.ALPHABET)) {
            return getNextFunction();
        }
        if (isCurrentCharFrom(Number.ALPHABET)) {
            return getNextNumber();
        }
        if (isCurrentCharFrom(Control.ALPHABET)) {
            return getNextControl();
        }
        return null;
    }

    private Argument getNextArgument() throws ArgumentIndexOutOfRangeException {
        StringBuilder builder = new StringBuilder("x");
        position++;
        builder.append(getNextNode(Number.ALPHABET));
        String value = builder.toString();
        return aparser.parse(value);
    }

    private Operation getNextOperation() throws WordNotFoundException {
        String value = Character.toString(sequence[position]);
        position++;
        return oparser.parse(value);
    }

    private Control getNextControl() throws WordNotFoundException {
        String value = Character.toString(sequence[position]);
        position++;
        return ceparser.parse(value);
    }

    private Function getNextFunction() throws WordNotFoundException {
        String value = getNextNode(Function.ALPHABET);
        return fparser.parse(value);
    }

    private Number getNextNumber() {
        StringBuilder builder = new StringBuilder();
        builder.append(getNextNode(Number.ALPHABET));
        if (isCurrentCharFrom(Number.DECIMAL_MARK)) {
            builder.append('.');
            position++;
            builder.append(getNextNode(Number.ALPHABET));
        }
        String value = builder.toString();
        return nparser.parse(value);
    }

    private String getNextNode(String from) {
        StringBuilder builder = new StringBuilder();
        while (isCurrentCharFrom(from)) {
            builder.append(sequence[position]);
            position++;
        }
        return builder.toString();
    }

    private boolean isCurrentCharFrom(String from) {
        return null != from && position < sequence.length && from.indexOf(sequence[position]) >= 0;
    }

    /**
     * Interface for word parsers.
     *
     * @param <T> param class, which extends <code>Word</code> instance
     */
    private interface WordParser<T extends Word> {
        /**
         * Finds the <code>Word</code>, which corresponds to <code>value</code>.
         *
         * @param value value for parse
         * @return <code>Word</code> instance
         * @throws WordNotFoundException throws, if there is no word corresponds to <code>value</code>
         */
        public T parse(String value) throws ParserException;
    }

    /** Function parser */
    private class FunctionParser implements WordParser<Function> {
        public Function parse(String value) throws WordNotFoundException {
            Function function = Function.getFunction(value.toLowerCase());
            if (null == function) {
                String message = MessageFormat.format("Wrong function element: {0}, possible variants are: {1}.", value, Function.VARIANTS);
                throw new WordNotFoundException(message, position);
            }
            return function;
        }
    }

    /** Number parser */
    private class NumberParser implements WordParser<Number> {
        public Number parse(String value) {
            double dvalue = Double.parseDouble(value); // there is no exceptions here
            return new Number(dvalue);
        }
    }

    /** Operation parser */
    private class OperationParser implements WordParser<Operation> {
        public Operation parse(String value) throws WordNotFoundException {
            Operation operation = Operation.getOperation(value);
            if (null == operation) {
                String message = MessageFormat.format("Wrong operation element: {0}, possible variants: {1}.", value, Operation.ALPHABET);
                throw new WordNotFoundException(message, position);
            }
            return operation;
        }
    }

    /** Control parser */
    private class ControlParser implements WordParser<Control> {
        public Control parse(String value) throws WordNotFoundException {
            Control control = Control.getControlElement(value);
            if (null == control) {
                String message = MessageFormat.format("Wrong control element: {0}. Possible variants: {1}", value, Control.ALPHABET);
                throw new WordNotFoundException(message, position);
            }
            return control;
        }
    }

    /** Argument parser */
    private class ArgumentParser implements WordParser<Argument> {
        public Argument parse(String value) throws ArgumentIndexOutOfRangeException {
            String ivalue = value.substring(1);
            if (0 == ivalue.length()) {
                return new Argument(0);
            }
            int i;
            try {
                i = Integer.parseInt(ivalue);
            } catch (NumberFormatException e) { // exception may occur, if index is more than Integer.MAX_VALUE
                throw new ArgumentIndexOutOfRangeException(position);
            }
            return new Argument(i - 1);
        }
    }

}
